package com.shopping.huaju.commodity.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.shopping.huaju.commodity.dao.CategoryBrandRelationDao;
import com.shopping.huaju.commodity.entity.CategoryBrandRelationEntity;
import com.shopping.huaju.commodity.entity.vo.Catalog2VO;
import com.shopping.huaju.commodity.service.CategoryBrandRelationService;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shopping.huaju.common.utils.PageUtils;
import com.shopping.huaju.common.utils.Query;

import com.shopping.huaju.commodity.dao.CategoryDao;
import com.shopping.huaju.commodity.entity.CategoryEntity;
import com.shopping.huaju.commodity.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    private CategoryBrandRelationDao categoryBrandRelationDao;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;

    @Override
    public List<CategoryEntity> listTree() {

        // 思路分析 - 步骤
        // 1. 查出 所有的分类数据
        List<CategoryEntity> entities = baseMapper.selectList(null);
        // 2. 组装成层级树形解构[需要使用到 java8 的 stream api + 递归操作]
        Stream<CategoryEntity> stream = entities.stream();
        List<CategoryEntity> res = stream.filter(categoryEntity -> {
            //  2.1 过滤filter，返回所有1级分类
            return categoryEntity.getCatLevel() == 1;
        }).map(categoryEntity -> {
            //  2.2 进行map映射操作，给每个分类设置对应的子分类[递归操作]
            categoryEntity.setChildrenCategories(getChildrenCategories(categoryEntity, entities));
            return categoryEntity;
        }).sorted((category1, category2) -> {
            //  2.3 进行排序 sorted 操作
            CategoryEntity entity1 = (CategoryEntity) category1;
            CategoryEntity entity2 = (CategoryEntity) category2;
            return (entity2.getSort() == null ? 0 : entity2.getSort()) - (entity1.getSort() == null ? 0 : entity1.getSort()) ;
        }).collect(//  2.4 将处理好的数据收集/转换到集合
                Collectors.toList());
        //3. 返回带有层级关系数据-树形格式
        return res;
    }

    private List<CategoryEntity> getChildrenCategories(CategoryEntity root,List<CategoryEntity> all){
        Stream<CategoryEntity> stream = all.stream();
        List<CategoryEntity> children = stream.filter(categoryEntity -> {
            //说明 :
            // categoryEntity.getParentId() 和 root.getId() 返回的都是 Long类型
            // Long是引用类型/包装类型 == 比较的是对象地址是否相等
            // 在Long引用类中存在缓存的机制 -128~127 会在Long类型中提前缓存好，如果是返回 -128~127 数据范围，同一个值都是返回同一Long对象
            // 超过 -128~127 范围，返回同一个值，已经是不同对象了。
            // 因此使用 ==  categoryEntity.getParentId() 和 root.getId()在范围外是不相等的，即使值是相等的
            //return categoryEntity.getParentId() == root.getId();
            return Objects.equals(categoryEntity.getParentId(), root.getId());
        }).map(categoryEntity -> {
            categoryEntity.setChildrenCategories(getChildrenCategories(categoryEntity, all));
            return categoryEntity;
        }).sorted((category1, category2) -> {
            return (category2.getSort() == null ? 0 : category2.getSort()) -
                    (category1.getSort() == null ? 0 : category1.getSort());
        }).collect(Collectors.toList());
        return children;
    }

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public Long[] getCascaderCategoryId(Long categoryId) {
        //创建一个 List 方便存放 层级id
        List<Long> list = new ArrayList<>();
        CategoryEntity categoryEntity = this.getById(categoryId);
        //迭代 倒序获取 层级id 直到 某个categoryEntity的父层级 不存在
        while (categoryEntity != null){
            list.add(categoryEntity.getId());
            categoryEntity = this.getById(categoryEntity.getParentId());
        }
        //对list 进行倒叙
        Collections.reverse(list);
        return list.toArray(new Long[]{});
    }
         //删除某个分区下的所有数据
    //根据分类的父类id返回对应的 分类集合
    @Override
    public List<CategoryEntity> getCategoriesByParentCategory(Long parent_id) {
        return this.baseMapper.selectList(
                new QueryWrapper<CategoryEntity>().eq("parent_id",parent_id));
    }

    @Override
    public List<CategoryEntity> getCategoriesByParentCategoryInList(
            List<CategoryEntity> categoryEntityList, Long parent_id) {
        List<CategoryEntity> categoryEntities = categoryEntityList.stream().
                filter(entity -> entity.getParentId().equals(parent_id))
                .collect(Collectors.toList());
        return categoryEntities;
    }

    /**
     * 每一个需要缓存的数据我们都来指定要放到那个名字的缓存。【缓存的分区(按照业务类型分)】
     * 代表当前方法的结果需要缓存，如果缓存中有，方法都不用调用，如果缓存中没有，会调用方法。最后将方法的结果放入缓存
     * 默认行为
     *      如果缓存中有，方法不再调用
     *      key是默认生成的:缓存的名字::SimpleKey::[](自动生成key值)
     *      缓存的value值，默认使用jdk序列化机制，将序列化的数据存到redis中
     *      默认时间是 -1：
     *
     *   自定义操作：key的生成
     *      指定生成缓存的key：key属性指定，接收一个Spel
     *      指定缓存的数据的存活时间:配置文档中修改存活时间
     *      将数据保存为json格式
     *
     *
     * 4、Spring-Cache的不足之处：
     *  1）、读模式
     *      缓存穿透：查询一个null数据。解决方案：缓存空数据
     *      缓存击穿：大量并发进来同时查询一个正好过期的数据。解决方案：加锁 ? 默认是无加锁的;使用sync = true来解决击穿问题
     *      缓存雪崩：大量的key同时过期。解决：加随机时间。加上过期时间
     *  2)、写模式：（缓存与数据库一致）
     *      1）、读写加锁。
     *      2）、引入Canal,感知到MySQL的更新去更新Redis
     *      3）、读多写多，直接去数据库查询就行
     *
     *  总结：
     *      常规数据（读多写少，即时性，一致性要求不高的数据，完全可以使用Spring-Cache）：写模式(只要缓存的数据有过期时间就足够了)
     *      特殊数据：特殊设计
     *
     *  原理：
     *      CacheManager(RedisCacheManager)->Cache(RedisCache)->Cache负责缓存的读写
     * @return
     */
    @Cacheable(value = {"category"},key = "#root.method.name",sync = true)
    //自定义接口 -- 返回第一层分类
    @Override
    public List<CategoryEntity> getLevel1Categories() {
        return getCategoriesByParentCategory(0L);
    }
    //自定义接口 -- 先获取的全部数据再筛选返回第一层分类
    @Override
    public List<CategoryEntity> getLevel1CategoriesInList(List<CategoryEntity> categoryEntityList) {
        return this.getCategoriesByParentCategoryInList(categoryEntityList,0L);
    }
    public Map<String, List<Catalog2VO>> getCatalogLevelMapFromDB() {
            String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
            if(!StringUtils.isEmpty(catalogJSON)){
                return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {});
            }
            List<CategoryEntity> categoryAllEntities = this.baseMapper.selectList(null);
            //获取第一层级分类信息
            List<CategoryEntity> level1Categories = this.getLevel1CategoriesInList(categoryAllEntities);
            if (level1Categories == null || level1Categories.isEmpty()) { //如果没有第一级分类，直接返回空的Map
                return new HashMap<>();
            }
            //进行构建 Map<String, List<Catalog2VO>> 对象 String是第一季分类的id List<Catalog2VO>是其对应的二级分类列表
            Map<String, List<Catalog2VO>> categoryLevelMap = level1Categories.stream().collect(Collectors.toMap(
                    key -> key.getId().toString(),
                    value -> {
                        //构建 List<Catalog2VO> 对象
                        List<Catalog2VO> catalog2VOList = new ArrayList<>();
                        //1. 获取对应一级分类下的所有二级分类
                        List<CategoryEntity> level2Categories
                                = getCategoriesByParentCategoryInList(categoryAllEntities, value.getId());
                        //2. 如过没有二级分类,直接返回空的 List<Catalog2VO>
                        if (level2Categories == null || level2Categories.isEmpty()) {
                            return catalog2VOList;
                        }
                        //3. 如果有二级分类进行构建 List<Catalog2VO>
                        catalog2VOList = level2Categories.stream().map(item -> {
                            //4. 构建 Catalog2VO
                            Catalog2VO catalog2VO = new Catalog2VO();
                            catalog2VO.setCatalog1Id(value.getId().toString());
                            catalog2VO.setId(item.getId().toString());
                            catalog2VO.setName(item.getName());
                            //5. 构建 List<Catalog3VO>
                            List<Catalog2VO.Catalog3VO> catalog3VOList = new ArrayList<>();
                            List<CategoryEntity> level3Categories =
                                    getCategoriesByParentCategoryInList(categoryAllEntities, item.getId());
                            //6. 如果没有三级分类,直接返回空的 List<Catalog3VO>
                            if (level3Categories != null && !level3Categories.isEmpty()) {
                                //7. 如果有三级分类，构建三级分类信息List<Catalog2VO.Catalog3VO>
                                catalog3VOList = level3Categories.stream().map(item3 -> {
                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO();
                                    catalog3VO.setCatalog2Id(item.getId().toString());
                                    catalog3VO.setName(item3.getName());
                                    catalog3VO.setId(item3.getId().toString());
                                    return catalog3VO;
                                }).collect(Collectors.toList());
                            }
                            catalog2VO.setCatalog3List(catalog3VOList);
                            return catalog2VO;
                        }).collect(Collectors.toList());
                        return catalog2VOList;
                    }
            ));
            String s = JSON.toJSONString(categoryLevelMap);
            stringRedisTemplate.opsForValue().set("catalogJSON",s);
            return categoryLevelMap;
    }
    @Cacheable(value = "category",key = "#root.methodName")
    public Map<String, List<Catalog2VO>> getCatalogLevelMap(){
        List<CategoryEntity> categoryAllEntities = this.baseMapper.selectList(null);
        //获取第一层级分类信息
        List<CategoryEntity> level1Categories = this.getLevel1CategoriesInList(categoryAllEntities);
        if (level1Categories == null || level1Categories.isEmpty()) { //如果没有第一级分类，直接返回空的Map
            return new HashMap<>();
        }
        //进行构建 Map<String, List<Catalog2VO>> 对象 String是第一季分类的id List<Catalog2VO>是其对应的二级分类列表
        Map<String, List<Catalog2VO>> categoryLevelMap = level1Categories.stream().collect(Collectors.toMap(
                key -> key.getId().toString(),
                value -> {
                    //构建 List<Catalog2VO> 对象
                    List<Catalog2VO> catalog2VOList = new ArrayList<>();
                    //1. 获取对应一级分类下的所有二级分类
                    List<CategoryEntity> level2Categories
                            = getCategoriesByParentCategoryInList(categoryAllEntities, value.getId());
                    //2. 如过没有二级分类,直接返回空的 List<Catalog2VO>
                    if (level2Categories == null || level2Categories.isEmpty()) {
                        return catalog2VOList;
                    }
                    //3. 如果有二级分类进行构建 List<Catalog2VO>
                    catalog2VOList = level2Categories.stream().map(item -> {
                        //4. 构建 Catalog2VO
                        Catalog2VO catalog2VO = new Catalog2VO();
                        catalog2VO.setCatalog1Id(value.getId().toString());
                        catalog2VO.setId(item.getId().toString());
                        catalog2VO.setName(item.getName());
                        //5. 构建 List<Catalog3VO>
                        List<Catalog2VO.Catalog3VO> catalog3VOList = new ArrayList<>();
                        List<CategoryEntity> level3Categories =
                                getCategoriesByParentCategoryInList(categoryAllEntities, item.getId());
                        //6. 如果没有三级分类,直接返回空的 List<Catalog3VO>
                        if (level3Categories != null && !level3Categories.isEmpty()) {
                            //7. 如果有三级分类，构建三级分类信息List<Catalog2VO.Catalog3VO>
                            catalog3VOList = level3Categories.stream().map(item3 -> {
                                Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO();
                                catalog3VO.setCatalog2Id(item.getId().toString());
                                catalog3VO.setName(item3.getName());
                                catalog3VO.setId(item3.getId().toString());
                                return catalog3VO;
                            }).collect(Collectors.toList());
                        }
                        catalog2VO.setCatalog3List(catalog3VOList);
                        return catalog2VO;
                    }).collect(Collectors.toList());
                    return catalog2VOList;
                }
        ));
        return categoryLevelMap;
    }
    /**
     * 级联更新所有关联的数据
     *
     * @CacheEvict:失效模式
     * @CachePut:双写模式，需要有返回值
     * 1、同时进行多种缓存操作：@Caching
     * 2、指定删除某个分区下的所有数据 @CacheEvict(value = "category",allEntries = true)
     * 3、存储同一类型的数据，都可以指定为同一分区
     * @param category
     */
    // @Caching(evict = {
    //         @CacheEvict(value = "category",key = "'getLevel1Categories'"),
    //         @CacheEvict(value = "category",key = "'getCatalogLevelMap'")
    // })
    @CacheEvict(value = "category",allEntries = true)
    @Override
    @Transactional
    public void updateCategoryAndRelation(CategoryEntity category) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("catalogJson-lock");
        //创建写锁
        RLock rLock = readWriteLock.writeLock();

        try {
            rLock.lock();
            this.updateById(category);
            this.baseMapper.updateCategory(category.getId(),category.getName());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
    }

    @Override
    @Transactional
    public void deleteCategoryAndRelation(Long[] ids) {
        this.removeByIds(Arrays.asList(ids));
        categoryBrandRelationDao.delete(new QueryWrapper<CategoryBrandRelationEntity>().
                in("category_id",Arrays.asList(ids)));
    }

    //锁
    public Map<String, List<Catalog2VO>> getCatalogLevelMapFromDbWithLocalLock(){
        synchronized (this) {
            //得到锁以后，我们应该再去缓存中确定一次，如果没有才需要继续查询
            return getCatalogLevelMapFromDB();
        }
    }

    public Map<String, List<Catalog2VO>> getCatalogLevelMapFromDbWithRedisLock() {

        //1、占分布式锁。去redis占坑      设置过期时间必须和加锁是同步的，保证原子性（避免死锁）
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300, TimeUnit.SECONDS);
        if (lock) {
            System.out.println("获取分布式锁成功...");
            Map<String, List<Catalog2VO>> dataFromDb = null;
            try {
                //加锁成功...执行业务
                dataFromDb = getCatalogLevelMapFromDB();
            } finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

                //删除锁
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDb;
        } else {
            System.out.println("获取分布式锁失败...等待重试...");
            //加锁失败...重试机制
            //休眠一百毫秒
            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
            return getCatalogLevelMapFromDbWithRedisLock();     //自旋的方式
        }
    }

    public Map<String, List<Catalog2VO>> getCatalogLevelMapFromDbWithRedissonLock() {

        //1、占分布式锁。去redis占坑
        //（锁的粒度，越细越快:具体缓存的是某个数据，11号商品） product-11-lock
        //RLock catalogJsonLock = redissonClient.getLock("catalogJson-lock");
        //创建读锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("catalogJson-lock");

        RLock rLock = readWriteLock.readLock();

        Map<String, List<Catalog2VO>> dataFromDb = null;
        try {
            rLock.lock();
            //加锁成功...执行业务
            dataFromDb = getCatalogLevelMapFromDB();
        } finally {
            rLock.unlock();
        }
        //先去redis查询下保证当前的锁是自己的
        //获取值对比，对比成功删除=原子性 lua脚本解锁
        // String lockValue = stringRedisTemplate.opsForValue().get("lock");
        // if (uuid.equals(lockValue)) {
        //     //删除我自己的锁
        //     stringRedisTemplate.delete("lock");
        // }

        return dataFromDb;

    }
    public Map<String, List<Catalog2VO>> getCatalogLevelMap2() {
        //给缓存中放json字符串，拿出的json字符串，反序列为能用的对象

        /**
         * 1、空结果缓存：解决缓存穿透问题
         * 2、设置过期时间(加随机值)：解决缓存雪崩
         * 3、加锁：解决缓存击穿问题
         */

        //1、加入缓存逻辑,缓存中存的数据是json字符串
        //JSON跨语言。跨平台兼容。
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String catalogJson = ops.get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)) {
            System.out.println("缓存不命中...查询数据库...");
            //2、缓存中没有数据，查询数据库
            Map<String, List<Catalog2VO>> catalogJsonFromDb = getCatalogLevelMapFromDbWithRedissonLock();

            return catalogJsonFromDb;
        }

        System.out.println("缓存命中...直接返回...");
        //转为指定的对象
        Map<String, List<Catalog2VO>> result = JSON.parseObject(catalogJson,new TypeReference<Map<String, List<Catalog2VO>>>(){});
        return result;
    }
}